<template>
  <div ref="dragLineRef" class="drag-line" @mousedown.stop="handleMousedown">
    <div
      v-for="(item, index) in valueList"
      :key="index"
      class="value-item"
      :style="{ left: `${item.s}px`, width: `${item.d}px` }"
      @mousedown.stop="handleDragMousedown($event, index)"
    >
      <div class="before" @mousedown.stop="handleLineMousedown($event, index, 'before')">
        <div class="show-time">{{ lineToTime(item.s, item.d).s }}</div>
      </div>
      <div class="after" @mousedown.stop="handleLineMousedown($event, index, 'after')">
        <div class="show-time">{{ lineToTime(item.s, item.d).e }}</div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { nextTick, onMounted, PropType, ref, watch } from 'vue';

const emits = defineEmits(['update:modelValue']);

const props = defineProps({
  // [{s:'',e:''}]
  modelValue: {
    type: Array as PropType<any>,
    default: () => {
      return [];
    },
  },
});

const dragLineRef = ref<HTMLDivElement>();

const options = { gap: 2, total: 24, totalHours: 24 * 60 };
const stickList = ref<any[]>([]);
let dragData: any = {};
const valueList = ref<any[]>([]);

// 初始化拖拽条
const render = () => {
  const width = dragLineRef.value.offsetWidth;
  const perWidth = width / options.total;

  for (let i = 0; i < options.total + 1; i++) {
    const data = { left: perWidth * i, num: i, show: !(i / options.gap), s: i === 0, e: i === options.total };
    stickList.value.push(data);
  }
};

// 设置数据
const setData = () => {
  const arr: any[] = [];

  props.modelValue.forEach((item: any) => {
    const data = timeToLine(item.s, item.e);
    arr.push(data);
  });

  valueList.value = arr;
};

// 传递参数
const emitsData = () => {
  const list = valueList.value.map((item) => {
    return lineToTime(item.s, item.d);
  });
  emits('update:modelValue', list);
};

// 在最外层容器中拖动
const handleMousedown = (e: any) => {
  e.preventDefault();

  const { pageX } = e;
  const dragLineBoundingClientRect = dragLineRef.value.getBoundingClientRect();

  dragData.s = pageX - dragLineBoundingClientRect.left;
  dragData.sPageX = pageX;
  dragData.d = 0;
  dragData.left = dragLineBoundingClientRect.left;
  dragData.index = valueList.value.length;

  valueList.value.push(dragData);
  document.addEventListener('mousemove', handleMousemove);
  document.addEventListener('mouseup', handleMouseup);
};

const handleMousemove = (e: any) => {
  const { pageX } = e;
  const distance = pageX - dragData.sPageX;
  const position = pageX - dragData.left;

  if (dragData.s + distance > dragLineRef.value.offsetWidth) return;

  // 反方向
  if (distance < 0) {
    if (position < 0) return;
    dragData.s = position;
  }

  dragData.d = Math.abs(distance);

  refreshData();
};

const handleMouseup = () => {
  dragData = {};
  emitsData();

  document.removeEventListener('mousemove', handleMousemove);
  document.removeEventListener('mouseup', handleMouseup);
};

// 在拖动条上拖动
const handleDragMousedown = (e: any, i: number) => {
  e.preventDefault();

  const { pageX } = e;
  const data = valueList.value[i];

  dragData.sPageX = pageX;
  dragData.index = i;
  dragData.s = data.s;
  dragData.d = data.d;
  dragData.oldS = dragData.s;

  document.addEventListener('mousemove', handleDragMousemove);
  document.addEventListener('mouseup', handleDragMouseup);
};

const handleDragMousemove = (e: any) => {
  const { pageX } = e;
  const distance = pageX - dragData.sPageX;
  const s = dragData.oldS + distance;

  if (s < 0 || s + dragData.d > dragLineRef.value.offsetWidth) return;

  dragData.s = s;

  refreshData();
};

const handleDragMouseup = () => {
  dragData = {};
  emitsData();
  document.removeEventListener('mousemove', handleDragMousemove);
  document.removeEventListener('mouseup', handleDragMouseup);
};

const refreshData = () => {
  valueList.value[dragData.index] = { ...valueList.value[dragData.index], ...dragData };
  valueList.value = valueList.value.map((item) => item);
};

// 在拖动条的临界值上拖动
const handleLineMousedown = (e: any, i: number, type: string) => {
  e.preventDefault();
  const { pageX } = e;
  const dragLineBoundingClientRect = dragLineRef.value.getBoundingClientRect();
  const data = valueList.value[i];

  dragData.sPageX = pageX;
  dragData.index = i;
  dragData.s = data.s;
  dragData.d = data.d;
  dragData.oldD = data.d;
  dragData.e = dragData.s + dragData.d;
  dragData.type = type;
  dragData.left = dragLineBoundingClientRect.left;

  document.addEventListener('mousemove', handleLineMousemove);
  document.addEventListener('mouseup', handleLineMouseup);
};

const handleLineMousemove = (e: any) => {
  const { pageX } = e;

  if (dragData.type === 'before') {
    const distance = pageX - dragData.sPageX - dragData.oldD;
    if (distance < 0) {
      const s = pageX - dragData.left;
      if (s < 0) return;

      dragData.s = s;
      dragData.d = Math.abs(dragData.e - dragData.s);
    } else {
      if (dragData.s + distance > dragLineRef.value.offsetWidth) return;
      dragData.d = distance;
    }
  } else {
    const distance = pageX - dragData.sPageX + dragData.oldD;

    if (distance < 0) {
      const s = pageX - dragData.left;

      if (s < 0) return;
      dragData.s = s;
    } else if (distance + dragData.s > dragLineRef.value.offsetWidth) return;

    dragData.d = Math.abs(distance);
  }

  refreshData();
};

const handleLineMouseup = () => {
  dragData = {};
  emitsData();
  document.removeEventListener('mousemove', handleLineMousemove);
  document.removeEventListener('mouseup', handleLineMouseup);
};

// 长度转时间（起始时间）
const lineToTime = (s: number, d: number) => {
  const totalWidth = dragLineRef.value.offsetWidth;
  const sPercent = s / totalWidth;
  const dPercent = d / totalWidth;

  const startTime = getTimeStr(Math.ceil(options.totalHours * sPercent));
  const endTime = getTimeStr(Math.ceil(options.totalHours * (sPercent + dPercent)));

  return { s: startTime, e: endTime };
};

// 获得时间
const getTimeStr = (startMinutes: any) => {
  let hours: any = Math.floor(startMinutes / 60);
  let minutes: any = (startMinutes - hours * 60).toFixed(0);

  hours = hours < 10 ? `0${hours}` : hours;
  minutes = minutes < 10 ? `0${minutes}` : minutes;

  return `${hours}:${minutes}`;
};

// 时间转长度（起始位置和距离）
const timeToLine = (st: string, et: string) => {
  const s = getLineValue(st);
  const e = getLineValue(et);
  const d = e - s;
  return { s, d };
};

const getLineValue = (v: string) => {
  const arr = v.split(':');
  const num = parseInt(arr[0], 10) * 60 + parseInt(arr[1], 10);
  const percent = num / options.totalHours;

  return dragLineRef.value.offsetWidth * percent;
};

onMounted(() => {
  render();
});

watch(
  () => props.modelValue,
  async () => {
    await nextTick();
    setData();
  },
  { immediate: true, deep: true },
);
</script>

<style lang="less" scoped>
.drag-line {
  width: 100%;
  height: 34px;
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);

  .value-item {
    position: absolute;
    left: 0;
    top: 0;
    width: 0;
    height: 100%;
    z-index: 6;
    background-color: #d2e1fc;
    cursor: move;
    border-radius: 30px;

    .before,
    .after {
      position: absolute;
      top: 0;
      width: 10px;
      height: 100%;
      cursor: ew-resize;

      &::before {
        content: '';
        width: 9px;
        height: 9px;
        border-radius: 100%;
        background: var(--td-brand-color);
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        border: 1px solid #fff;
      }

      .show-time {
        position: absolute;
        left: 50%;
        transform: translateX(-50%);
        top: -30px;
        height: 24px;
        padding: 3px;
        border-radius: 3px;
        background-color: white;
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 12px;
        color: #16171a;
        box-shadow: 0 5px 14px 0 rgb(0 0 0 / 10%);
      }
    }

    .before {
      left: 0;

      &::before {
        left: calc(50% + 6px);
      }
    }

    .after {
      right: 0;

      &::before {
        left: calc(50% - 6px);
      }
    }
  }
}
</style>
